/********************************************************************\
 * qof-backend.hpp Declare QofBackend class                         *
 * Copyright 2016 John Ralls <jralls@ceridwen.us>                   *
 *                                                                  *
 * This program is free software; you can redistribute it and/or    *
 * modify it under the terms of the GNU General Public License as   *
 * published by the Free Software Foundation; either version 2 of   *
 * the License, or (at your option) any later version.              *
 *                                                                  *
 * This program is distributed in the hope that it will be useful,  *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 * GNU General Public License for more details.                     *
 *                                                                  *
 * You should have received a copy of the GNU General Public License*
 * along with this program; if not, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 *                                                                  *
\********************************************************************/
/** @addtogroup Object
    @{ */
/** @addtogroup Object_Private
    Private interfaces, not meant to be used by applications.
    @{ */
/** @name  Backend_Private
   Pseudo-object defining how the engine can interact with different
   back-ends (which may be SQL databases, or network interfaces to
   remote QOF servers. File-io is just one type of backend).

   The callbacks will be called at the appropriate times during
   a book session to allow the backend to store the data as needed.

   @file qofbackend-p.h
   @brief private api for data storage backend
   @author Copyright (c) 2000,2001,2004 Linas Vepstas <linas@linas.org>
   @author Copyright (c) 2005 Neil Williams <linux@codehelp.co.uk>
@{ */

#ifndef __QOF_BACKEND_HPP__
#define __QOF_BACKEND_HPP__
extern "C"
{
#include "qofbackend.h"
#include "qofbook.h"
#include "qofinstance-p.h"
#include "qofquery.h"
#include "qofsession.h"
#include <gmodule.h>
}

#include <string>
#include <algorithm>
#include <vector>
/* NOTE: The following comments were musings by the original developer about how
 * some additional API might work. The compile/free/run_query functions were
 * implemented for the DBI backend but never put into use; the rest were never
 * implemented. They're here as something to consider if we ever decide to
 * implement them.
 *
 * The compile_query() method compiles a QOF query object into
 *    a backend-specific data structure and returns the compiled
 *    query. For an SQL backend, the contents of the query object
 *    need to be turned into a corresponding SQL query statement, and
 *    sent to the database for evaluation.
 *
 * The free_query() method frees the data structure returned from
 *    compile_query()
 *
 * The run_query() callback takes a compiled query (generated by
 *    compile_query) and runs the query in across the backend,
 *    inserting the responses into the engine. The database will
 *    return a set of splits and transactions and this callback needs
 *    to poke these into the account-group hierarchy held by the query
 *    object.
 *
 *    For a network-communications backend, essentially the same is
 *    done, except that this routine would convert the query to wire
 *    protocol, get an answer from the remote server, and push that
 *    into the account-group object.
 *
 *    The returned list of entities can be used to build a local
 *    cache of the matching data.  This will allow the QOF client to
 *    continue functioning even when disconnected from the server:
 *    this is because it will have its local cache of data from which to work.
 *
 * The events_pending() routines should return true if there are
 *    external events which need to be processed to bring the
 *    engine up to date with the backend.
 *
 * The process_events() routine should process any events indicated
 *    by the events_pending() routine. It should return TRUE if
 *    the engine was changed while engine events were suspended.
 *
 * For support of book partitioning, use special "Book"  begin_edit()
 *    and commit_edit() QOF_ID types.
 *
 *    Call the book begin() at the beginning of a book partitioning.  A
 *    'partitioning' is the splitting off of a chunk of the current
 *    book into a second book by means of a query.  Every transaction
 *    in that query is to be moved ('transferred') to the second book
 *    from the existing book.  The argument of this routine is a
 *    pointer to the second book, where the results of the query
 *    should go.
 *
 *    Call the book commit() to complete the book partitioning.
 *
 *    After the begin(), there will be a call to run_query(), followed
 *    probably by a string of object calls, and completed by commit().
 *    It should be explicitly understood that the results of that
 *    run_query() precisely constitute the set of objects that are to
 *    be moved between the initial and the new book. This specification
 *    can be used by a clever backend to avoid excess data movement
 *    between the server and the QOF client, as explained below.
 *
 *    There are several possible ways in which a backend may choose to
 *    implement the book splitting process.  A 'file-type' backend may
 *    choose to ignore this call, and the subsequent query, and simply
 *    write out the new book to a file when the commit() call is made.
 *    By that point, the engine will have performed all of the
 *    nitty-gritty of moving transactions from one book to the other.
 *
 *    A 'database-type' backend has several interesting choices.  One
 *    simple choice is to simply perform the run_query() as it
 *    normally would, and likewise treat the object edits as usual.
 *    In this scenario, the commit() is more or less a no-op.
 *    This implementation has a drawback, however: the run_query() may
 *    cause the transfer of a <b>huge</b> amount of data between the backend
 *    and the engine.  For a large dataset, this is quite undesirable.
 *    In addition, there are risks associated with the loss of network
 *    connectivity during the transfer; thus a partition might terminate
 *    half-finished, in some indeterminate state, due to network errors.
 *    It might be difficult to recover from such errors: the engine does
 *    not take any special safety measures during the transfer.
 *
 *    Thus, for a large database, an alternate implementation
 *    might be to use the run_query() call as an opportunity to
 *    transfer entities between the two books in the database,
 *    and not actually return any new data to the engine.  In
 *    this scenario, the engine will attempt to transfer those
 *    entities that it does know about.  It does not, however,
 *    need to know about all the other entities that also would
 *    be transferred over.  In this way, a backend could perform
 *    a mass transfer of entities between books without having
 *    to actually move much (or any) data to the engine.
 *
 * To support configuration options from the frontend, the backend
 *    can be passed a KvpFrame - according to the allowed options
 *    for that backend, using load_config(). Configuration can be
 *    updated at any point - it is up to the frontend to load the
 *    data in time for whatever the backend needs to do. e.g. an
 *    option to save a new book in a compressed format need not be
 *    loaded until the backend is about to save. If the configuration
 *    is updated by the user, the frontend should call load_config
 *    again to update the backend.
 *
 *    Backends are responsible for ensuring that any supported
 *    configuration options are initialised to usable values.
 *    This should be done in the function called from backend_new.
 */


typedef enum
{
    LOAD_TYPE_INITIAL_LOAD,
    LOAD_TYPE_LOAD_ALL
} QofBackendLoadType;

using GModuleVec = std::vector<GModule*>;
struct QofBackend
{
public:
    /* For reasons that aren't a bit clear, using the default constructor
     * sometimes initializes m_last_err incorrectly with Xcode8 and a 32-bit
     * build unless the initialization is stepped-through in a debugger.
     */
    QofBackend() :
        m_percentage{nullptr}, m_fullpath{}, m_last_err{ERR_BACKEND_NO_ERR},
        m_error_msg{} {}
    QofBackend(const QofBackend&) = delete;
    QofBackend(const QofBackend&&) = delete;
    virtual ~QofBackend() = default;
/**
 *    Open the file or connect to the server.
 *    @param session The QofSession that will control the backend.
 *    @param book_id The book's string identifier.
 *    @param ignore_lock indicates whether the single-user lock on the backend
 *    should be cleared.  The typical GUI sequence leading to this is:
 *    (1) GUI attempts to open the backend by calling this routine with
 *    ignore_lock false.
 *    (2) If backend error'ed BACKEND_LOCK, then GUI asks user what to do.
 *    (3) if user answers 'break & enter' then this routine is called again with
 *    ignore_lock true.
 *    @param create indicates whether this routine should create a new
 *    'database', if it doesn't already exist. For example, for a file-backend,
 *    this would create the file, if it didn't already exist.  For an SQL
 *    backend, this would create the database (the schema) if it didn't already
 *    exist.  This flag is used to implement the 'SaveAs' GUI, where the user
 *    requests to save data to a new backend.
 *
 *    @param force works with create to force creating a new database even if
 *    one already exists at the same URI.
 */
    virtual void session_begin(QofSession *session, const char* book_id,
                               bool ignore_lock, bool create, bool force) = 0;
    virtual void session_end() = 0;
/**
 *    Load the minimal set of application data needed for the application to be
 *    operable at initial startup.  It is assumed that the application will
 *    perform a 'run_query()' to obtain any additional data that it needs.  For
 *    file-based backends, it is acceptable for the backend to return all data
 *    at load time; for SQL-based backends, it is acceptable for the backend to
 *    return no data.
 *
 *    Thus, for example, the old GnuCash postgres backend returned the account
 *    tree, all currencies, and the pricedb, as these were needed at startup.
 *    It did not have to return any transactions whatsoever, as these were
 *    obtained at a later stage when a user opened a register, resulting in a
 *    query being sent to the backend. The current DBI backend on the other hand
 *    loads the entire database into memory.
 *
 *    (Its OK to send over entities at this point, but one should
 *    be careful of the network load; also, its possible that whatever
 *    is sent is not what the user wanted anyway, which is why its
 *    better to wait for the query).
 */
    virtual void load (QofBook*, QofBackendLoadType) = 0;
/**
 *    Called when the engine is about to make a change to a data structure. It
 *    could provide an advisory lock on data, but no backend does this.
 */
    virtual void begin(QofInstance*) {}
/**
 *    Commits the changes from the engine to the backend data storage.
 */
    virtual void commit (QofInstance*) {}
/**
 *    Revert changes in the engine and unlock the backend.
 */
    virtual void rollback(QofInstance*) {}
/**
 *    Synchronizes the engine contents to the backend.
 *    This should done by using version numbers (hack alert -- the engine
 *    does not currently contain version numbers).
 *    If the engine contents are newer than what is in the backend, the
 *    data is stored to the backend. If the engine contents are older,
 *    then the engine contents are updated.
 *
 *    Note that this sync operation is only meant to apply to the
 *    current contents of the engine. This routine is not intended
 *    to be used to fetch entity data from the backend.
 *
 *    File based backends tend to use sync as if it was called dump.
 *    Data is written out into the backend, overwriting the previous
 *    data. Database backends should implement a more intelligent
 *    solution.
 */
    virtual void sync(QofBook *) = 0;
/** Perform a sync in a way that prevents data loss on a DBI backend.
 */
    virtual void safe_sync(QofBook *) = 0;
/**   Extract the chart of accounts from the current database and create a new
 *   database with it. Implemented only in the XML backend at present.
 */
    virtual void export_coa(QofBook *) {}
/** Set the error value only if there isn't already an error already.
 */
    void set_error(QofBackendError err);
/** Retrieve the currently-stored error and clear it.
 */
    QofBackendError get_error();
/** Report if there is an error.
 */
    bool check_error();
/** Set a descriptive message that can be displayed to the user when there's an
 * error.
 */
    void set_message(std::string&&);
/** Retrieve and clear the stored error message.
 */
    const std::string&& get_message();
/** Store and retrieve a backend-specific function for determining the progress
 * in completing a long operation, for use with a progress meter.
 */
    void set_percentage(QofBePercentageFunc pctfn) { m_percentage = pctfn; }
    QofBePercentageFunc get_percentage() { return m_percentage; }
/** Retrieve the backend's storage URI.
 */
    std::string get_uri() { return m_fullpath; }
/**
 * Class methods for dynamically loading the several backends and for freeing
 * them at shutdown.
 */
    static bool register_backend(const char*, const char*);
    static void release_backends();
protected:
    QofBePercentageFunc m_percentage;
    /** Each backend resolves a fully-qualified file path.
     * This holds the filepath and communicates it to the frontends.
     */
    std::string m_fullpath;
private:
    static GModuleVec c_be_registry;
    QofBackendError m_last_err;
    std::string m_error_msg;
};

/* @} */
/* @} */
/* @} */

#endif /* __QOF_BACKEND_HPP__ */
